NexusPi Git Node
Commit be9beb84440eed8606dec98f8eaecc746512904e
Parents : 39d7773
Author : James L <jrl290@gmail.com>
Date : 2026-07-27T20:46:32-04:00
Add rnprobe responder toggle to captive portal
- FirewallState.probe_enabled + EEPROM address 0x23C (FirewallMode.h)
- Toggle + hash display in captive portal Diagnostics section
(FirewallConfig.h)
- Bridge firewall_probe_enabled to Transport.cpp (RNode_Firmware.ino)
- Guard probe destination registration behind the toggle
(lib/microReticulum/src/Transport.cpp)
The probe destination hash is shown in the portal when the node
hash is cached in RTC (i.e., after first normal boot). The user
can copy it for use with: rnprobe <hash>.rnstransport.probe <hash>
Default: disabled. Enable in the captive portal to respond to
standard rnprobe utility pings.
Changes
4 files changed, 71 insertions(+), 6 deletions(-)
Diff
diff --git a/FirewallConfig.h b/FirewallConfig.h
index 97cc9be..1a0b3c4 100755
--- a/FirewallConfig.h
+++ b/FirewallConfig.h
@@ -370,6 +370,35 @@ static void config_send_html() {
html += String(firewall_state.mdns_hostname);
html += F("'>");
+ // ── Diagnostics / rnprobe Section ──
+ html += F(
+ "<h2>🔍 Diagnostics (rnprobe)</h2>"
+ "<p class='note'>When enabled, this node responds to <code>rnprobe</code> utility pings "
+ "from other Reticulum nodes. rnprobe measures round-trip time and packet loss over "
+ "the network path. The probe destination is identity-backed and requires no link setup.</p>"
+ "<label>rnprobe Responder</label>"
+ "<select name='probe_en'>"
+ );
+ html += F("<option value='1'");
+ if (firewall_state.probe_enabled) html += F(" selected");
+ html += F(">Enabled</option>");
+ html += F("<option value='0'");
+ if (!firewall_state.probe_enabled) html += F(" selected");
+ html += F(">Disabled</option>");
+ html += F("</select>");
+
+ html += F("<div class='node-hash' style='margin-top:8px;'><span class='nh-label'>📨 Probe Destination</span><code>");
+ if (rtc_node_hash_magic == NODE_HASH_RTC_MAGIC && rtc_node_hash_hex[0] != '\\0') {
+ html += String(rtc_node_hash_hex);
+ html += F(".rnstransport.probe");
+ } else {
+ html += F("<span style='color:#888;font-style:italic;'>Available after first normal boot</span>");
+ }
+ html += F("</code></div>");
+ html += F("<p class='note'>Run: <code>rnprobe <hash>.rnstransport.probe <hash></code> "
+ "from any reachable Reticulum node. The hash is also printed in the serial log "
+ "as <code>PROBE-DST</code> at boot.</p>");
+
// ── LoRa Radio Section ──
html += F(
"<h2>📻 LoRa Radio</h2>"
@@ -776,6 +805,9 @@ static void config_handle_save() {
}
}
+ // ── rnprobe responder ──
+ firewall_state.probe_enabled = (config_server->arg("probe_en").toInt() == 1);
+
// Save boundary config to EEPROM
firewall_save_config();
diff --git a/FirewallMode.h b/FirewallMode.h
index a6b3556..5b9c8be 100755
--- a/FirewallMode.h
+++ b/FirewallMode.h
@@ -105,6 +105,7 @@
#define ADDR_CONF_LT_AL 0x150 // Long-term airtime limit (1 byte, percent * 10)
#define ADDR_CONF_MDNS_EN 0x151 // mDNS enable flag (1 byte; 0x73 = enabled, 0xFF = unset/default-enabled)
#define ADDR_CONF_MDNS_NAME 0x152 // Custom mDNS hostname (33 bytes, null-terminated; empty = auto)
+#define ADDR_CONF_PROBE_EN 0x23C // rnprobe responder enable (1 byte; 0x73 = enabled, 0xFF = unset/disabled)
// Extra backbone slots 1-3 (slot 0 remains in the legacy BTCP/BHOST/BHPORT
// fields for backward compatibility with existing devices).
#define ADDR_CONF_BSLOT_BASE 0x173
@@ -166,6 +167,12 @@ struct FirewallState {
// hyphen/digit at save time.
char mdns_hostname[33];
+ // rnprobe responder — when enabled, the Transport instance registers a
+ // well-known destination that responds to rnprobe utility probes.
+ // The destination hash is deterministic: hash(transport_identity_hash +
+ // name_hash("rnstransport", "probe")). Default: disabled.
+ bool probe_enabled;
+
// Runtime state
bool wifi_connected;
bool ap_tcp_connected; // Local TCP server (LAN) has client
@@ -430,6 +437,12 @@ inline void firewall_load_config() {
}
firewall_state.mdns_hostname[32] = '\0';
+ // rnprobe responder — disabled by default (0xFF = unset)
+ {
+ uint8_t probe_byte = EEPROM.read(config_addr(ADDR_CONF_PROBE_EN));
+ firewall_state.probe_enabled = (probe_byte == FIREWALL_ENABLE_BYTE);
+ }
+
// Reset runtime state
firewall_state.packets_bridged_lora_to_tcp = 0;
firewall_state.packets_bridged_tcp_to_lora = 0;
@@ -530,6 +543,10 @@ inline void firewall_save_config() {
}
EEPROM.write(config_addr(ADDR_CONF_MDNS_NAME + 32), 0x00);
+ // rnprobe responder
+ EEPROM.write(config_addr(ADDR_CONF_PROBE_EN),
+ firewall_state.probe_enabled ? FIREWALL_ENABLE_BYTE : 0x00);
+
EEPROM.write(config_addr(ADDR_CONF_APP_MARKER0), FIREWALL_APP_MARKER0);
EEPROM.write(config_addr(ADDR_CONF_APP_MARKER1), FIREWALL_APP_MARKER1);
EEPROM.write(config_addr(ADDR_CONF_APP_VERSION), FIREWALL_APP_VERSION);
diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino
index 89fb6a5..9338d30 100755
--- a/RNode_Firmware.ino
+++ b/RNode_Firmware.ino
@@ -273,6 +273,8 @@ RNS::FileSystem filesystem(RNS::Type::NONE);
#ifdef FIREWALL_MODE
// Firewall mode: TCP backbone interface + state
FirewallState firewall_state = {};
+// Bridge to microReticulum Transport.cpp (avoids header coupling)
+bool firewall_probe_enabled = false;
RNS::Interface tcp_rns_interfaces[FIREWALL_BACKBONE_SLOTS] = {
RNS::Interface(RNS::Type::NONE),
RNS::Interface(RNS::Type::NONE),
@@ -1002,6 +1004,9 @@ void setup() {
boundary_nominal_path_table_maxpersist = RNS::Transport::probe_destination_enabled();
firewall_load_config();
+ // Bridge probe toggle to Transport (read before Transport::start())
+ firewall_probe_enabled = firewall_state.probe_enabled;
+
// Set up IFAC on the LoRa interface if configured
if (firewall_state.ifac_enabled &&
(firewall_state.ifac_netname[0] != '\0' || firewall_state.ifac_passphrase[0] != '\0')) {
diff --git a/lib/microReticulum/src/Transport.cpp b/lib/microReticulum/src/Transport.cpp
index ad5659b..448ae77 100755
--- a/lib/microReticulum/src/Transport.cpp
+++ b/lib/microReticulum/src/Transport.cpp
@@ -20,6 +20,11 @@ using namespace RNS;
using namespace RNS::Type::Transport;
using namespace RNS::Utilities;
+// ── Firewall-mode extern: set by RTNode firmware before Transport::start() ──
+#ifdef FIREWALL_MODE
+extern bool firewall_probe_enabled;
+#endif
+
// ── Flat-map helpers (vector<pair<Bytes,T>> replaces std::map<Bytes,T>) ────
// Eliminates per-element tree-node allocation. Linear search is fine
// for N ≤ ~1000 on ESP32.
@@ -369,12 +374,18 @@ static inline bool is_resource_ctx(uint8_t ctx) {
// delivery proof. PROVE_ALL makes Transport send that proof
// automatically — no custom packet handler needed.
// The destination hash is well-known: identity.hash + hash("rnstransport", "probe").
- Destination probe_destination(Transport::identity(), Type::Destination::IN, Type::Destination::SINGLE, APP_NAME, "probe");
- probe_destination.accepts_links(false);
- probe_destination.set_proof_strategy(Type::Destination::PROVE_ALL);
- _control_destinations.insert(probe_destination);
- _control_hashes.insert(probe_destination.hash());
- NOTICE("PROBE-DST: " + probe_destination.hash().toHex().substr(0,8) + " — responding to rnprobe requests");
+ // In firewall mode, gated by the captive-portal toggle.
+#ifdef FIREWALL_MODE
+ if (firewall_probe_enabled)
+#endif
+ {
+ Destination probe_destination(Transport::identity(), Type::Destination::IN, Type::Destination::SINGLE, APP_NAME, "probe");
+ probe_destination.accepts_links(false);
+ probe_destination.set_proof_strategy(Type::Destination::PROVE_ALL);
+ _control_destinations.insert(probe_destination);
+ _control_hashes.insert(probe_destination.hash());
+ NOTICE("PROBE-DST: " + probe_destination.hash().toHex().substr(0,8) + " — responding to rnprobe requests");
+ }
_jobs_running = false;
Served by rngit 1.5.0 - Generated in 0.03s